#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include "main.h"




/***** BISON *****/

extern int yyparse();
extern int yylineno;

/* yyerror:  fonction importée par Bison et a fournir explicitement. Elle
 * est appelée quand Bison détecte une erreur syntaxique.
 * Ici on se contente d'un message minimal.
 */
void yyerror(char *s) {
  printf("Ligne %d: %s\n", yylineno, s);
}




/***** VARIABLES GLOBALES *****/

/* globalClasses: nécessaire pour l'analyseur syntaxique. */
pclasse globalClasses, currentClasse = nil(classe);

FILE *fd = nil(FILE), *fdout = nil(FILE);
char *fname;
int nbITE = 0; // Pour pouvoir avoir des labels nouveaux à chaque if.
int fp = 0; //Pour toujours connaître la position des variables locales




/***** MAIN *****/

int main(int argc, char **argv) {
	int fi;

	if (argc < 2) {
		fprintf(stderr, "Fichier programme manquant\n");
		exit(ERROR_FILE);
	}

	if ((fi = open(argv[1], O_RDONLY)) == -1) {
		fprintf(stderr, "Je n'arrive pas a ouvrir: %s\n", argv[1]);
		exit(ERROR_FILE);
	}


	// Redirige l'entrée standard sur le fichier.
	close(0);
	dup(fi);
	close(fi);

	if (argc >= 3) { // Fichier dans lequel écrire le code
		if ((fdout = fopen(argv[2],  "w+")) == NULL) {
			perror(argv[2]);
			exit(ERROR_FILE);
		}
	}
	else {
		if ((fdout = fopen("code.txt", "w+")) == NULL) { // Fichier par défaut.
			perror("code.txt");
			exit(ERROR_FILE);
		}
	}

	globalClasses = initClasses();
  /* yyparse: appel à l'analyseur syntaxique. Lance l'analyse syntaxique de
   * tout le source, en appelant yylex au fur et a mesure. Execute les
   * actions semantiques en parallele avec les mouvements de type REDUCE de
   * l'analyseur syntaxique.
   * yyparse renvoie 0 si le source est syntaxiquement correct, une valeur
   * differente de 0 en cas d'erreur lexicale ou syntaxique.
   * Comme l'interpretation globale est automatiquement lancee par les actions
   * associees aux mouvements REDUCE, une fois que yyparse a termine il n'y
   * a plus rien a faire.
   */
	if (yyparse()) {
		fprintf(stderr, "Programme Incorrect \n");
		if(fdout != nil(FILE))
			fclose(fdout);
		return ERROR_SYNTAX;
	}

	if(fdout != nil(FILE))
		fclose(fdout);
	return 0;
}




/***** GENERATION DE CODE INTERMEDIAIRE *****/

/* Procédure de lancement de la production du code. */
void executeMain(pclasse classContext, pbloc blocMain) {
	pclasse PC;
	pmeth PM;
	pbloc PB;
	int numMeth, gp = 0;

	ecrireLigne("JUMP init");

	PC = classContext;
	while(PC != nil(classe)) { //Définition des gp des classes
		PC->gp = gp;

		PC = PC->suiv;
		gp++;
	}

	PC = classContext;
	while(PC != nil(classe)) { //Création des squelettes des classes
		fprintf(fdout,"--Classe %s\n", PC->nom);

		currentClasse = PC;
		PM = PC->lmeth;

		while(PM != nil(meth)) { //Création des squelettes des méthodes
			fprintf(fdout,"--Methode %s\n", PM->nom);
			fprintf(fdout,"%s_%s: NOP\n", PC->nom, PM->nom);

			//Méthode imprimer()
			if(!strcmp(PC->nom, "Entier") || !strcmp(PC->nom, "Chaine")) {
				ecrireLigne("PUSHL -1"); //L'objet appelant est en fp - 1
				if(!strcmp(PC->nom, "Entier"))
					ecrireLigne("WRITEI");
				else
					ecrireLigne("WRITES");
				ecrireLigne("PUSHS \"\\n\""); //Juste pour faire joli
				ecrireLigne("WRITES");
			}
			else {
				PB = PM->corps;
				fp = 0;
				ecrireBloc(PB, nil(var), PM->args, classContext);
			}

			//Pour sauvegarder la valeur de retour
			ecrireLigne("STOREL -1");

			ecrireLigne("RETURN");
			PM = PM->suiv;
		}

		PC = PC->suiv;
	}

	ecrireLigne("\n");
	ecrireLigne("init: NOP --Initialisation et allocation des classes");
	PC = classContext;
	while(PC != nil(classe)) {
		if(!strcmp(PC->nom, "Entier") || !strcmp(PC->nom, "Chaine")) {
			fprintf(fdout, "--Allocation de la classe %s\n", PC->nom);
			fprintf(fdout, "ALLOC 1 -- Classe %s\n", PC->nom);
		}
		else {
			fprintf(fdout, "--Allocation de la classe %s\n", PC->nom);
			fprintf(fdout, "ALLOC %d -- Classe %s\n", PC->nbMeths, PC->nom);
		}

		PM = PC->lmeth;
		numMeth = 0;
		while(PM != nil(meth)) {
			ecrireLigne("DUPN 1");
			fprintf(fdout, "PUSHA %s_%s\n", PC->nom, PM->nom);	//On empile l'adresse de la méthode
			fprintf(fdout, "STORE %d\n", numMeth); //On stocke cette adresse

			numMeth++;
			PM = PM->suiv;
		}

		PC = PC->suiv;
	}

	currentClasse = nil(classe);

	ecrireLigne("--main");
	ecrireLigne("START");
	fp = 0;
	ecrireBloc(blocMain, nil(var), nil(var), classContext);
	ecrireLigne("STOP");
}


/* Permet d'écrire une ligne de code intermédiaire dans le fichier
 * de sortie.
 */
void ecrireLigne(char* ligne) {
	fprintf(fdout, "%s\n", ligne);
}


/* Procédure d'écriture d'un bloc de code */
pclasse ecrireBloc(pbloc bloc, pvar context, pvar args, pclasse classContext) {
	pexpr PE, tmpExpr, tmpExpr2;
	pvar PV;
	pclasse PC, returnType;
	int oldfp = fp;

	//On place chaque variable locale dans la pile
	PV = bloc->localContext;
	while(PV != nil(var)) {

		PC = PV->type;

		/*Les variables sont des références vers un objet. Le premier champ
		contient la référence et le second l'adresse du squelette (méthodes
		de la classe)*/
		fprintf(fdout, "ALLOC 2 -- Variable %s\n", PV->nom);

		ecrireLigne("DUPN 1");

		//Squelette de la classe
		fprintf(fdout, "PUSHG %d\n", PC->gp);

		//On stocke le squelette de la classe dans la "seconde case"
		ecrireLigne("STORE 1");

		PV->fp = fp;

		fp++;

		//On initialise la variable si possible
		if(PV->val != nil(expr)) {
			ecrireLigne("DUPN 1");
			fp++;
			returnType = ecrireExpr(PV->val->expre, context, args, classContext);
			if(returnType != PV->type) {
				fprintf(stderr, "Erreur : Conflit de type à l'initialisation de la variable %s\n", PV->nom);
				exit(3);
			}
			ecrireLigne("STORE 0");
			fp = fp - 2;
		}

		if(nil(var) == PV->suiv) {
			PV->suiv = context; //Il faut ajouter les variables locales du bloc au
												 //contexte déjà existant
			break;
		}
		else
			PV = PV->suiv;
	}

	//On inverse la liste d'expressions
	PE = bloc->listExpr;
	tmpExpr2 = PE->suiv;
	PE->suiv = nil(expr);
	tmpExpr = PE;
	while(tmpExpr2 != nil(expr)) {
		PE = tmpExpr2;
		tmpExpr2 = PE->suiv;
		PE->suiv = tmpExpr;
		tmpExpr = PE;
	}
	bloc->listExpr = PE;

	//On écrit chaque expression du bloc
	while(PE->suiv != nil(expr)) {
		ecrireExpr(PE->expre, bloc->localContext, args, classContext);
		ecrireLigne("POPN 1"); //Si ce n'est pas la dernière expression on ne garde
		                       //pas le résultat
		fp--;

		PE = PE->suiv;
	}

	returnType = ecrireExpr(PE->expre, bloc->localContext, args, classContext);

	//On enlève toutes les variables locales
	if(fp != oldfp + 1) {
		fprintf(fdout, "STOREL %d\n", oldfp);

		fprintf(fdout, "POPN %d\n", fp - oldfp - 2);

		fp = oldfp;
	}

	return returnType;
}


/* Permet d'écrire une expression en code intermédiaire. Le premier
 * argument est bien évidemment l'expression. Le second est le
 * contexte de l'expression, donc les variables locales au bloc de
 * l'expression. Le troisième est la liste des arguments, donc des
 * variables locales au bloc dans le cas d'une méthode. Le quatrième
 * est la liste de toutes les classes. L'argument fp permet de connaître
 * le prochain emplacement libre dans la pile (la hauteur par rapport au fp).
 */
pclasse ecrireExpr(parbre exp, pvar context, pvar args, pclasse classContext) {
	pvar tmpVar, tmpVar2;
	pmeth tmpMeth;
	pexpr tmpExpr;
	int nbArgs = 0, numArg = 1, numChamp = 1, numMeth = 1, compteur = 0, oldfp, oldfp2;
	pclasse tmpClasse, tmpClasse2, returnType;

	switch(exp->op) {
		case Id:	//TODO VERIF TYPES - Recherche dans le contexte de currentClasse
			tmpVar = context;
			while(tmpVar != nil(var)) { //On recherche si c'est une variable locale au bloc
				if(!strcmp(tmpVar->nom, exp->gauche.S)) {
					fprintf(fdout, "PUSHL %d\n", tmpVar->fp);

					/*Pour les entiers et chaines on charge la valeur
					et pour les autres ont charge le pointeur*/
					ecrireLigne("LOAD 0");
					fp++;

					return tmpVar->type;
				}
				tmpVar = tmpVar->suiv;
			}

			//On compte le nombre d'arguments
			tmpVar = args;
			while(tmpVar != nil(var)) {
				nbArgs++;
				tmpVar = tmpVar->suiv;
			}

			tmpVar = args;
			numArg = nbArgs;
			while(tmpVar != nil(var)) { //On recherche si c'est un argument
				if(!strcmp(tmpVar->nom, exp->gauche.S)) {
					//L'argument numArg se trouve en fp-numArg-1
					fprintf(fdout, "PUSHL -%d\n", numArg+1);

					ecrireLigne("LOAD 0");
					fp++;

					return tmpVar->type;
				}
				numArg--;
				tmpVar = tmpVar->suiv;
			}

			if(currentClasse) {
				tmpVar = currentClasse->lvar;
				numChamp = currentClasse->nbChamps;
				while(tmpVar != nil(var)) { //On recherche si c'est un attribut de classe
					if(!strcmp(tmpVar->nom, exp->gauche.S)) {
						//On récupère le champ numChamp de l'objet
						ecrireLigne("PUSHL -1");

						fprintf(fdout, "LOAD %d\n", numChamp - 1);

						ecrireLigne("LOAD 0");
						fp++;

						return tmpVar->type;
					}

					numChamp--;
					tmpVar = tmpVar->suiv;
				}
			}

		  //La variable n'existe pas
			fprintf(stderr, "Variable non declarée: %s\n", exp->gauche.S);
			exit(3);

		case CsteInt:
			fprintf(fdout, "PUSHI %d\n", exp->gauche.C.entier);
			fp++;
			return GetClasseIn("Entier", globalClasses);

		case CsteSt:
			fprintf(fdout, "PUSHS %s\n", exp->gauche.C.chaine);
			fp++;
			return GetClasseIn("Chaine", globalClasses);

		case EQ:
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur = nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("EQUAL");
			fp--;
			return returnType;

		case NEQ:
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur != nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("EQUAL");
			ecrireLigne("NOT"); //La machine ne possède pas de NEQUAL
			fp--;
			return returnType;

		case GT:
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur > nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("SUP");
			fp--;
			return returnType;

		case GE:
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur >= nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("SUPEQ");
			fp--;
			return returnType;

		case LT:
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur < nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("INF");
			fp--;
			return returnType;

		case LE:
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur <= nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("INFEQ");
			fp--;
			return returnType;

		case '+':
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur + nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("ADD");
			fp--;
			return returnType;

		case '-':
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur - nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("SUB");
			fp--;
			return returnType;

		case '*':
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur * nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("MUL");
			fp--;
			return returnType;

		case '/':
			returnType = GetClasseIn("Entier", globalClasses);

			if(ecrireExpr(exp->gauche.A, context, args, classContext) != returnType
			|| ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur / nécessite deux Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("DIV");
			fp--;
			return returnType;

		case Unary:
			returnType = GetClasseIn("Entier", globalClasses);

			if(exp->gauche.E == '-') {
				ecrireLigne("PUSHI 0");
				fp++;
			}

			if(ecrireExpr(exp->droit.A, context, args, classContext) != returnType) {
				fprintf(stderr, "Erreur - Conflit de type, l'opérateur unaire %c nécessite un Entier\n", exp->gauche.E);
				exit(ERROR_TYPE);
			}

			if(exp->gauche.E == '-') {
				ecrireLigne("SUB");
				fp--;
			}

			return returnType;

		case New:
			//On place les arguments du constructeur, comme des variables locales
			tmpVar = exp->gauche.L->args;
			tmpExpr = exp->droit.X;
			oldfp = fp;
			compteur = 1;

			if(tmpVar != nil(var)) {
				while(tmpVar != nil(var)) {
					//On place l'argument dans la pile
					fprintf(fdout, "ALLOC 2 -- Argument %s\n", tmpVar->nom);
					tmpVar->fp = fp;
					ecrireLigne("DUPN 1");
					fprintf(fdout, "PUSHG %d\n", tmpVar->type->gp);
					ecrireLigne("STORE 1");

					//On lui affecte la valeur passée en paramètre du constructeur
					ecrireLigne("DUPN 1");
					fp = fp + 2;
					if(ecrireExpr(tmpExpr->expre, context, args, classContext) != tmpVar->type) {
						fprintf(stderr, "Erreur - Conflit de type, type %s attendu pour l'argument #%d de la classe %s\n", tmpVar->type->nom, compteur, exp->gauche.L->nom);
						exit(ERROR_TYPE);
					}
					ecrireLigne("STORE 0");
					fp = fp - 2;

					compteur++;
					tmpExpr = tmpExpr->suiv;
					tmpVar = tmpVar->suiv;
				}

				//On ajoute ces variables au contexte
				tmpVar = exp->gauche.L->args;
				if(tmpVar != nil(var)) {
					while(tmpVar->suiv != nil(var)) {
						tmpVar = tmpVar->suiv;
					}

					tmpVar->suiv = context;
					context = exp->gauche.L->args;
				}
			}

			//On alloue l'objet avec une taille égale au nombre de champs de la classe
			fprintf(fdout, "ALLOC %d\n", exp->gauche.L->nbChamps);
			fp++;

			//On initialise chaque champ du nouvel objet
			compteur = 0;
			tmpVar2 = exp->gauche.L->lvar;
			while(tmpVar2 != nil(var)) {
				ecrireLigne("DUPN 1");

				fprintf(fdout, "ALLOC 2 -- Champ %s\n", tmpVar2->nom);
				//Squelette de la classe
				ecrireLigne("DUPN 1");
				fprintf(fdout, "PUSHG %d\n", tmpVar2->type->gp);
				ecrireLigne("STORE 1");
				fp = fp + 2;

				if(tmpVar2->val != nil(expr)) {
					ecrireLigne("DUPN 1");
					fp++;
					ecrireExpr(tmpVar2->val->expre, context, args, classContext);
					ecrireLigne("STORE 0");
					fp = fp - 2;
				}

				fprintf(fdout, "STORE %d\n", exp->gauche.L->nbChamps - compteur - 1);
				fp = fp - 2;

				compteur++;
				tmpVar2 = tmpVar2->suiv;
			}

			if(exp->gauche.L->args != nil(var)) {
				//On ne veut pas détruire notre nouvel objet
				fprintf(fdout, "STOREL %d\n", oldfp);

				fprintf(fdout, "POPN %d\n", fp - oldfp - 2);
				fp = oldfp + 1;
			}

			return exp->gauche.L;

		case Bloc:
			return ecrireBloc(exp->gauche.B, context, args, classContext);

		case Aff:
			returnType = ecrireExpr(exp->gauche.A, context, args, classContext);
			ecrireLigne("DUPN 1"); //Permettra de garder le résultat de l'affectation
			fp++;
			tmpClasse = ecrireExpr(exp->droit.A, context, args, classContext);

			while(tmpClasse) {
				if(returnType == tmpClasse) {
					ecrireLigne("STORE 0");
					fp = fp - 2;
					return returnType;
				}

				tmpClasse = tmpClasse->herit;
			}

			fprintf(stderr, "Erreur - Conflit de type, les deux parties d'une affectation doivent être de même type (modulo l'héritage).\n");
			exit(ERROR_TYPE);

		case ITE:
			if(ecrireExpr(exp->gauche.A, context, args, classContext) != GetClasseIn("Entier", globalClasses)) {
				fprintf(stderr, "Erreur - Conflit de type, le conditionnel if nécessite un Entier\n");
				exit(ERROR_TYPE);
			}

			ecrireLigne("NOT");
			//Si le NOT empile 0, c'est que l'expression était non nulle (vraie)
			fprintf(fdout, "JZ Then%d\n", nbITE);
			fp--;

			//Expression du Else
			tmpClasse = ecrireExpr(exp->droit.A->droit.A, context, args, classContext);
			fprintf(fdout, "JUMP FinITE%d\n", nbITE);

			fp--;

			//Expression du Then
			fprintf(fdout, "Then%d: NOP\n", nbITE);
			tmpClasse2 = ecrireExpr(exp->droit.A->gauche.A, context, args, classContext);

			fprintf(fdout, "FinITE%d: NOP\n", nbITE);
			nbITE++;

			returnType = tmpClasse;
			while(tmpClasse) {
				if(tmpClasse == tmpClasse2)
					return tmpClasse2;

				tmpClasse = tmpClasse->herit;
			}

			tmpClasse = returnType;
			returnType = tmpClasse2;
			while(tmpClasse2) {
				if(tmpClasse == tmpClasse2)
					return tmpClasse;

				tmpClasse2 = tmpClasse2->herit;
			}

			fprintf(stderr, "Erreur - Les types de retour d'un ITE doivent être de même type (modulo l'héritage).");
			exit(ERROR_TYPE);

		case Envoi:	//TODO VERIF TYPES
			oldfp = fp;
			//On place l'objet appelant
			tmpClasse = ecrireExpr(exp->gauche.A, context, args, classContext);

			//On recherche la méthode à appeler
			tmpMeth = tmpClasse->lmeth;
			while(tmpMeth != nil(meth)) {
				if(!strcmp(tmpMeth->nom, exp->droit.F->nom))
					break;

				numMeth++;
				tmpMeth = tmpMeth->suiv;
			}

			//On place tous les arguments de la fonction, en commençant par les "derniers"
			tmpVar = tmpMeth->args;
			tmpExpr = exp->droit.F->params;
			compteur = 1;
			while(tmpVar != nil(var)) {
				fprintf(fdout, "ALLOC 2 -- Argument %s\n", tmpVar->nom);
				ecrireLigne("DUPN 1");
				fprintf(fdout, "PUSHG %d\n", tmpVar->type->gp);
				ecrireLigne("STORE 1");
				ecrireLigne("DUPN 1");

				fp = fp + 2;
				ecrireExpr(tmpExpr->expre, context, args, classContext);

				ecrireLigne("STORE 0");
				fp = fp - 2;

				ecrireLigne("SWAP"); //Pour garder l'objet appelant en haut de la pile

				compteur++;
				tmpExpr = tmpExpr->suiv;
				tmpVar = tmpVar->suiv;
			}

			//On charge enfin la méthode
			fprintf(fdout, "PUSHG %d\n", tmpClasse->gp);
			fprintf(fdout, "LOAD %d\n", numMeth - 1);

			oldfp2 = fp;
			fp = 0;
			ecrireLigne("CALL");
			fp = oldfp2;

			//On sauvegarde la valeur de retour et on enlève les arguments
			if(compteur != 1) {//Il n'y avait pas d'arguments 
				fprintf(fdout, "STOREL %d\n", oldfp);
				fprintf(fdout, "POPN %d\n", compteur - 2);
			}
			fp = oldfp + 1;

			return tmpMeth->retour;

		case Selec:
			tmpClasse = ecrireExpr(exp->gauche.A, context, args, classContext);

			//On recherche si l'Id est un champ de la classe précédente
			tmpVar = tmpClasse->lvar;
			while(tmpVar != nil(var)) {
				if(!strcmp(tmpVar->nom, exp->droit.A->gauche.S))
					break;

				numChamp++;
				tmpVar = tmpVar->suiv;
			}

			if(nil(var) == tmpVar) { //Ce n'est pas un champ
				//On recherche alors si c'est une méthode
				tmpMeth = tmpClasse->lmeth;
				while(tmpMeth != nil(meth)) {
					if((!strcmp(tmpMeth->nom, exp->droit.A->gauche.S)) && !tmpMeth->hasParams)
						break;

					numMeth++;
					tmpMeth = tmpMeth->suiv;
				}

				if(nil(meth) == tmpMeth) {
					fprintf(stderr, "Erreur : Variable/Méthode non déclarée %s\n", exp->droit.A->gauche.S);
					exit(3);
				}

				//On charge la méthode
				fprintf(fdout, "PUSHG %d\n", tmpClasse->gp);
				fprintf(fdout, "LOAD %d\n", numMeth - 1);

				oldfp = fp;
				fp = 0;
				ecrireLigne("CALL");
				fp = oldfp;

				return tmpMeth->retour;
			}

			//On charge le champ
			fprintf(fdout, "LOAD %d\n", tmpClasse->nbChamps - numChamp);
			ecrireLigne("LOAD 0");
			return tmpVar->type;

		case Cible:	//TODO VERIF TYPES
			if(Id == exp->gauche.A->op) {
				tmpVar = context;
				while(tmpVar != nil(var)) { //On recherche si c'est une variable locale au bloc
					if(!strcmp(tmpVar->nom, exp->gauche.A->gauche.S)) {
						fprintf(fdout, "PUSHL %d\n", tmpVar->fp);
						fp++;
						return tmpVar->type;
					}
					tmpVar = tmpVar->suiv;
				}

				tmpVar = args;
				while(tmpVar != nil(var)) { //On recherche si c'est un argument
					if(!strcmp(tmpVar->nom, exp->gauche.A->gauche.S)) {
						//L'argument numArg se trouve en fp-numArg-1
						fprintf(fdout, "PUSHL -%d\n", numArg+1);
						fp++;
						return tmpVar->type;
					}
				}

				if(currentClasse) {
					tmpVar = currentClasse->lvar;
					numChamp = currentClasse->nbChamps;
					while(tmpVar != nil(var)) { //On recherche si c'est un attribut de classe
						if(!strcmp(tmpVar->nom, exp->gauche.S)) {
							//On récupère le champ numChamp de l'objet
							ecrireLigne("PUSHL -1");

							fprintf(fdout, "LOAD %d\n", numChamp - 1);
							fp++;

							return tmpVar->type;
						}

						numChamp--;
						tmpVar = tmpVar->suiv;
					}
				}

				fprintf(stderr, "Variable non declarée: %s\n", exp->gauche.A->gauche.S);
				exit(3);
			}
			else if(Selec == exp->gauche.A->op) {
				tmpClasse = ecrireExpr(exp->gauche.A->gauche.A, context, args, classContext);

				//On recherche si l'Id est un champ de la classe précédente
				tmpVar = tmpClasse->lvar;
				while(tmpVar != nil(var)) {
					if(!strcmp(tmpVar->nom, exp->gauche.A->droit.A->gauche.S))
						break;

					numChamp++;
					tmpVar = tmpVar->suiv;
				}

				if(nil(var) == tmpVar) { //Ce n'est pas un champ
					//alors c'est une erreur (ça ne peut pas être une méthode)
					fprintf(stderr, "Champ non déclaré %s\n", exp->gauche.A->droit.A->gauche.S);
					exit(3);
				}

				//On charge le champ
				fprintf(fdout, "LOAD %d\n", tmpClasse->nbChamps - numChamp);

				return tmpVar->type;
			}

		case Self:	//TODO VERIF TYPES
			if(!currentClasse) { //Je sais pas si c'est suffisant comme vérif
				fprintf(stderr, "Erreur : Vous ne pouvez pas utiliser  \"self\" dans un tel contexte");
				exit(3);
			}

			ecrireLigne("PUSHL -1");
			fp++;

			return currentClasse;

		case Super:	//TODO VERIF TYPES
			//TODO

		default :
			fprintf(stderr, "ERREUR PAS COOL");
			exit(ERROR_SYNTAX);
	}
}




/***** FONCTIONS DE CONSTRUCTION DU SQUELETTE DU PROGRAMME *****/

/* Ajoute un nom de classe dans la liste lc */
pclasse AddClassName(char *nom, pclasse lc) {
	pclasse l = lc;

	while(l) {
		if (! strcmp(nom, l->nom)) {
			fprintf(stderr, "Erreur! Double déclaration de classe: %s\n", nom);
			exit(ERROR_CLASSE);
		}

		l = l->suiv;
	}

	classe *res = NEW(1, classe);
	res->nom = nom;
	res->suiv = lc;

	return res;
}


/* Vérifie la compatibilité de deux listes de paramètres. */
int typesMatchVV(pvar pv1, pvar pv2) {
	while(pv1 && pv2) {
		if(pv1->type != pv2->type)
			return FALSE;

		pv1 = pv1->suiv;
		pv2 = pv2->suiv;
	}

	if(pv1 || pv2)
		return FALSE;

	return TRUE;
}


/* Remplit une classe vide */
void FillClass(pclasse classToFill, pvar lvar, pmeth lmeth, pvar args, pext extends) {
	pclasse lc;
	pmeth lm = lmeth, tmpLM;
	pvar lv = lvar, tmpLV;
	int nbChamps = 0, nbMeths = 0;

	/* On ne peut pas définir de sous-classes aux classes prédéfinies. */
	if(extends && extends->cl && ((!strcmp(extends->cl->nom, "Entier")) || (!strcmp(extends->cl->nom, "Chaine")))) {
		fprintf(stderr, "Erreur! Héritage interdit (Entier ou Chaine) !");
		exit(ERROR_CLASSE);
	}

	/* Vérif. contextuelles sur les champs */
	while(lv) {
		/* Les champs ne peuvent pas avoir le même nom que les méthodes de la
		 * même classe. */
		while(lm) {
			if (! strcmp(lv->nom, lm->nom)) {
				fprintf(stderr, "Erreur! Conflit de nom \"%s\" dans la classe \"%s\"\n", lv->nom, classToFill->nom);
				exit(ERROR_CLASSE);
			}

			lm = lm->suiv;
		}

		nbChamps++;
		if(!extends) {
			lv = lv->suiv;
			continue;
		}

		lc = extends->cl;
		while(lc) {	/* Vérif. sur les classes héritées. */
			tmpLV = lc->lvar;
			lm = lc->lmeth;

			/* Les champs ne peuvent pas masquer ceux de la super-classe. */
			while(tmpLV) {
				if (! strcmp(lv->nom, tmpLV->nom)) {
					fprintf(stderr, "Erreur! Conflit de nom \"%s\" dans la classe \"%s\"\n", lv->nom, classToFill->nom);
					exit(ERROR_CLASSE);
				}

				tmpLV = tmpLV->suiv;
			}

			/* Dans le cas d'une constante qui "override". */
			if(lv->override && !lv->modifiable) {
				while(lm) {
					if (! strcmp(lv->nom, lm->nom)) {
						/* Un méthode avec paramètres ne peut pas être redéfinie
						 * par une constante. */
						if(lm->hasParams) {
							fprintf(stderr, "Erreur! Conflit de nom \"%s\" dans la classe \"%s\"\n", lv->nom, classToFill->nom);
							exit(ERROR_CLASSE);
						}
						else {	/* On override. */
							//TODO une action spéciale pour override de val sur def ?
						}
					}

					lm = lm->suiv;
				}
			}
			else {	/* Cas d'une variable ou d'une constante sans override */
				while(lm) {
					if (! strcmp(lv->nom, lm->nom)) {
						fprintf(stderr, "Erreur! Conflit de nom \"%s\" dans la classe \"%s\"\n", lv->nom, classToFill->nom);
						exit(ERROR_CLASSE);
					}

					lm = lm->suiv;
				}
			}

			lc = lc->herit;
		}

		lv = lv->suiv;
	}


	/* Vérif. contextuelles sur les méthodes */
	lm = lmeth;
	while(lm) {
		nbMeths++;
		if(!extends) {
			lm = lm->suiv;
			continue;
		}

		lc = extends->cl;

		while(lc) {
			lv = lc->lvar;
			tmpLM = lc->lmeth;

			/* Cas d'une méthode qui override. */
			if(lm->override) {
				while(lv) {
					/* On ne peut redéfinir une constante qui si la méthode n'a
					 * pas de paramètres (on ne parle pas du cas d'une méthode
					 * avec une liste de paramètres vide). */
					if ((!strcmp(lm->nom, lv->nom)) && (lm->hasParams || lv->modifiable)) {
						if(lm->hasParams || !lv->modifiable) {
							fprintf(stderr, "Erreur! Conflit de nom \"%s\" dans la classe \"%s\"\n", lm->nom, classToFill->nom);
							exit(ERROR_CLASSE);
						}
						else {
							//TODO Actions spéciales pour override de def sur val
						}
					}

					lv = lv->suiv;
				}

				while(tmpLM) {
					/* On ne redéfinie une méthode héritée que si il y'a
					 * des paramètres (inclus le cas de la liste de paramètres
					 * vide) et que les types (et le nombre) correspondent. */
					if (! strcmp(lm->nom, tmpLM->nom)) {
						if((!lm->hasParams) || lm->retour != tmpLM->retour || typesMatchVV(lm->args, tmpLM->args)) {
							fprintf(stderr, "Erreur! Conflit de nom \"%s\" dans la classe \"%s\"\n", lm->nom, classToFill->nom);
							exit(ERROR_CLASSE);
						}
						else {
							//TODO Actions spéciales pour override de def sur def
						}
					}

					tmpLM = tmpLM->suiv;
				}
			}
			else {
				while(lv) {
					if (! strcmp(lm->nom, lv->nom)) {
						fprintf(stderr, "Erreur! Conflit de nom \"%s\" dans la classe \"%s\"\n", lm->nom, classToFill->nom);
						exit(ERROR_CLASSE);
					}

					lv = lv->suiv;
				}

				while(tmpLM) {
					if (! strcmp(lm->nom, tmpLM->nom)) {
						fprintf(stderr, "Erreur! Conflit de nom \"%s\" dans la classe \"%s\"\n", lm->nom, classToFill->nom);
						exit(ERROR_CLASSE);
					}

					tmpLM = tmpLM->suiv;
				}
			}

			lc = lc->herit;
		}

		lm = lm->suiv;
	}

	classToFill->lvar = lvar;
	classToFill->lmeth = lmeth;
	classToFill->nbChamps = nbChamps;
	classToFill->nbMeths = nbMeths;
	classToFill->args = args;

	if(extends) {
		classToFill->herit = extends->cl;
		classToFill->params = extends->params;

		free(extends);
	}
	else {
		classToFill->herit = nil(classe);
		classToFill->params = nil(expr);
	}
}


/* Crée une structure méthode. */
pmeth MakeMeth(char *nom, int override, int hasParams, pvar args, pbloc blc, classe *retour) {
	pvar PV = args;
	int nbArgs = 0;

	while(PV != nil(var)) {
		nbArgs++;
		PV = PV->suiv;
	}

	pmeth newMeth = NEW(1, meth);
	newMeth->nom = nom;
	newMeth->override = override;
	newMeth->hasParams = hasParams;
	newMeth->args = args;
	newMeth->corps = blc;
	newMeth->retour = retour;

	return newMeth;
}


/* Crée une liste de variables. */
pmeth MakeLMeth(meth *methode, pmeth lm) {
	pmeth l = lm;

	while(l) {
		if (! strcmp(methode->nom, l->nom)) {
			fprintf(stderr, "Erreur! Double déclaration de méthode: %s\n", methode->nom);
			exit(ERROR_METH);
		}
		else
			l = l->suiv;
	}

	methode->suiv = lm;

	return methode;
}


/* Initialisation de la liste des classes en ajoutant les 2 classes prédéfinies:
 * Entier et Chaine. */
pclasse initClasses() {
	pclasse newClasses;

	newClasses = AddClassName("Entier", nil(classe));
	FillClass(newClasses, nil(var), MakeLMeth(MakeMeth("imprimer", FALSE, FALSE, nil(var), nil(bloc), newClasses), nil(meth)), nil(var), nil(ext));
	newClasses = AddClassName("Chaine", newClasses);
	FillClass(newClasses, nil(var), MakeLMeth(MakeMeth("imprimer", FALSE, FALSE, nil(var), nil(bloc), newClasses), nil(meth)), nil(var), nil(ext));

	return newClasses;
}


/* Crée une structure extends pour implémenter l'héritage. */
pext MakeEXTENDS(classe *cl, pexpr params) {
	pext newEXT = NEW(1, ext);
	newEXT->cl = cl;
	newEXT->params = params;

	return newEXT;
}


/* Fonction qui copie une liste de variables (pour les instances de classe notamment). */
pvar CopyLVar(pvar lv) {
	pvar newList, cursor;
	newList = NEW(1, var);
	cursor = newList;

	while(lv->suiv) {
		memcpy(cursor, lv, sizeof(var));

		lv = lv->suiv;
		cursor->suiv = NEW(1, var);
		cursor = cursor->suiv;
	}

	memcpy(cursor, lv, sizeof(var));
	cursor->suiv = NULL;

	return newList;
}


/* Fonction qui crée une instance de classe (pour mettre dans un var \o/)
 * On ne copie pas le lmeth, un simple pointeur suffit, les méthodes sont
 * communes à toutes les instances d'une même classe. Par contre, on copie
 * bien la liste des variables sinon ce serait des variables static !
 *
 * Cela fonctionne aussi pour faire une copie d'une instance */
classe *NewInstanceOf(classe *cl) {
	classe *newInstance = NEW(1, classe);

	memcpy(newInstance, cl, sizeof(classe));

	newInstance->lvar = CopyLVar(newInstance->lvar);

	return newInstance;
}


/* Une fonction pour récupérer un patron de classe dans une liste */
pclasse GetClasseIn(char *nom, pclasse lc) {
	while(lc) {
		if (! strcmp(nom, lc->nom))
			return lc;

		lc = lc->suiv;
	}

	fprintf(stderr, "Erreur! Classe \"%s\" hors de portée.\n", nom);
	return nil(classe);
}


/* Construit un couple (variable, valeur) apres avoir verifie que la variable
 * n'apparait pas deja dans la liste des variables declarees (lv) puisque
 * c'etait une contrainte de l'enonce.
 * Ajoute le nouveau couple en tete de liste et renvoie le tout.
 */
pvar MakeVar(char *nom, int override, classe *type, int modifiable, pexpr val) {
	pvar res = NEW(1, var);
	res->nom = nom;
	res->override = override;
	res->type = type;
	res->modifiable = modifiable;
	res->val = val;

	return res;
}


/* Crée une liste de variables. */
pvar MakeLVar(var *variable, pvar lv) {
	pvar l = lv;

	while(l) {
		if (! strcmp(variable->nom, l->nom)) {
			fprintf(stderr, "Erreur! Double déclaration de variable: %s\n", variable->nom);
			exit(ERROR_VAR);
		}
		else
			l = l->suiv;
	}

	variable->suiv = lv;

	return variable;
}


/* Une fonction pour récupérer une variable dans une liste */
var *GetVarIn(char *nom, pvar lv) {
	while(lv) {
		if (! strcmp(nom, lv->nom))
			return lv;

		lv = lv->suiv;
	}

	return nil(var);
}


/* Constructeur de feuille de type ID pour l'arbre de syntaxe abstraite:
 * on stocke l'id.
 */
parbre MakeId(char *nom) {
	parbre res = NEW(1, arbre);
	res->op = Id;
	res->gauche.S = nom;
	res->droit.A = nil(arbre);

	return res;
}


/* Idem pour une feuille de type CSTE entière : on stocke la valeur */
parbre MakeCsteInt(int val) {
  parbre res = NEW(1, arbre);
  res->op = CsteInt; res->gauche.C.entier = val; res->droit.A = nil(arbre);
  return(res);
}


/* Idem pour une feuille de type CSTE chaîne de caracètres : on stocke la valeur */
parbre MakeCsteString(char *val) {
  parbre res = NEW(1, arbre);
  res->op = CsteSt; res->gauche.C.chaine = val; res->droit.A = nil(arbre);
  return(res);
}


/* Constructeur pour un noeud interne: une etiquette pour l'operateur, et deux
 * sous-arbres.
 */
parbre MakeNoeud(char op, parbre g, parbre d) {
	parbre res = NEW(1, arbre);
	res->op = op;
	res->gauche.A = g;
	res->droit.A = d;

	return res;
}


/* Crée une structure bloc. */
pbloc CreateBloc(pexpr blc, pvar context) {
	pbloc newBloc = NEW(1, bloc);
	newBloc->listExpr = blc;
	newBloc->localContext = context;

	return newBloc;
}


/* Insère un bloc dans un arbre d'expression. */
parbre MakeBloc(pbloc blc) {
	parbre res = NEW(1, arbre);
	res->op = Bloc;
	res->gauche.B = blc;
	res->droit.A = nil(arbre);

	return res;
}


/* Constructeur pour le if_then_else qui oblige a coder un arbre ternaire avec
 * des arbres binaires. On cree un arbre racine d'etiquette ITE, de sous-arbre
 * gauche la condition et de sous-arbre droit un autre arbre d'etiquette
 * arbitraire (NOP) et dont les fils sont les arbres des parties then et else.
 */
parbre MakeITE(parbre pCond, parbre pThen, parbre pElse) {
	parbre p = MakeNoeud(NOP, pThen, pElse);

	return MakeNoeud(ITE, pCond, p);
}


/* Ajoute un opérateur unaire à un arbre d'expression. */
parbre MakeUnary(char unary, parbre d) {
	parbre res = NEW(1, arbre);
	res->op = Unary;
	res->gauche.E = unary;
	res->droit.A = d;

	return res;
}


/* Permet de construire une fonction appelée avec ses paramètres.
 */

pfct MakeFct(parbre Id, pexpr lexpr) {
	pfct res = NEW(1, fct);

	res->nom = Id->gauche.S;
	res->params = lexpr;

	return(res);
}


/* Permet de construire un arbre d'Identicateurs "chainés" pour représenter
 * les "x.y.f(z) etc..." du langage. Id1 sera plus "bas" dans l'arbre que
 * Id2.
 */
parbre MakeIdChaine(parbre Id1, parbre Id2) {
	parbre res = NEW(1, arbre);
	res->op = Id;
	res->gauche.S = Id2->gauche.S;
	res->droit.A = Id1;
	return(res);
}


/* Permet de créer une expression (ce qui n'est en fait qu'une liste d'expressions
 * avec une seule expression dedans.
 */
pexpr MakeExpr(parbre expre) {
	pexpr res = NEW(1, expr);
	res->expre = expre;
	res->suiv = nil(expr);
	return(res);
}


/* Permet d'ajouter une expression donnée (donc un arbre) à une liste
 * d'expression.
 */
pexpr MakeLExpr(parbre expre, pexpr lexpr) {
	pexpr res = MakeExpr(expre);
	res->suiv = lexpr;

	return(res);
}


/* Ajoute un NEW dans un arbre d'expressions. */
parbre MakeNEW(classe *cl, pexpr params) {
	parbre res = NEW(1, arbre);
	res->op = New;
	res->gauche.L = cl;
	res->droit.X = params;

	return res;
}


parbre MakeEnvoi(pexpr objet, pfct methode) {
	parbre res = NEW(1, arbre);
	res->op = Envoi;
	res->gauche.X = objet;
	res->droit.F = methode;

	return res;
}


parbre MakeSelec(pexpr objet, parbre id) {
	parbre res = NEW(1, arbre);
	res->op = Selec;
	res->gauche.X = objet;
	res->droit.A = id;

	return res;
}


parbre MakeCible(parbre cible) {
	parbre res = NEW(1, arbre);
	res->op = Cible;
	res->gauche.A = cible;
	res->droit.A = nil(arbre);

	return res;
}


/* lecture dynamique d'une valeur, indiquee par le GET dans une expression.
 * Exemple: x:= 3 + GET; begin x end
 * Voir comment GET est traite dans evalue()
 */
int lire() {
	char buf[50]; int res;

	if (fd == nil(FILE)) {
		fprintf(stderr, "Fichier de données manquant\n");
		exit(ERROR_FILE);
	}

	fgets(buf, 20, fd);
	sscanf(buf, "%d", &res);

	return res;
}
